#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <poll.h>

// For signals
#include <sys/wait.h>
#include <signal.h>
#include <setjmp.h>
// For uinput
#include <linux/input.h>
#include <linux/uinput.h>
// X11 libraries
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>

#include "include.h"
#include "HelperUtils.h"
// For Packet struct
#include "DataType.h"

static void sigTermHandler();

volatile int uInputSigTermCaught = 0;
sigjmp_buf environment;

int uInputMain(pid_t serialIOPid, int *dataPipe) {
    int i, returnCode;

	// Register a signal handler for SIGTERM
	struct sigaction newAction;
	newAction.sa_handler = sigTermHandler;
	sigemptyset(&newAction.sa_mask);
	newAction.sa_flags = SA_RESTART;
	if ( sigaction (SIGTERM, &newAction, NULL) == -1) {
		perror("FATAL: uInputMain(): sigaction() failed.");
		exit(1);
	}

    // Close write end of the data pipe
    close(dataPipe[1]);

    // Initialize UInput device
    int uInputDevice = open(UINPUTDEVICE, O_WRONLY | O_NDELAY);
    if (uInputDevice == -1) {
        dieWithError("FATAL: uInputMain(): Failed to open /dev/input/uinput");
    }
    
    // Create user input device in input subsystem
    struct uinput_user_dev mouse;
    memset(&mouse, 0, sizeof(mouse));
    strcpy(mouse.name, "Emulated Mouse 0");
    mouse.id.version = 4;
    mouse.id.bustype = BUS_USB;

    ioctl(uInputDevice, UI_SET_EVBIT, EV_KEY);
    ioctl(uInputDevice, UI_SET_EVBIT, EV_REL);
    ioctl(uInputDevice, UI_SET_RELBIT, REL_X);
    ioctl(uInputDevice, UI_SET_RELBIT, REL_Y);

    for (i=0; i<256; i++) {
        ioctl(uInputDevice, UI_SET_KEYBIT, i);
    }

    ioctl(uInputDevice, UI_SET_KEYBIT, BTN_MOUSE);

    if ((returnCode = write(uInputDevice, &mouse, sizeof(mouse))) == -1) {
        #if DEBUG >= 2
        fprintf(stderr, "DEBUG: uInputMain(): write() mouse struct returned %d.\n", returnCode);
        #endif
        dieWithError("FATAL: uInputMain(): write() mouse struct to uInputDevice failed.");
    }

    returnCode = (ioctl(uInputDevice, UI_DEV_CREATE));
    #if DEBUG >= 2
    fprintf(stderr, "DEBUG: uInputMain(): UI_DEV_CREATE returned %d.\n", returnCode);
    #endif
    
    Packet packet;  // Mouse packet read from SerialIO

    // Setup pollfd for poll()
    struct pollfd pollFd;
    memset(&pollFd, 0, sizeof(struct pollfd));
    pollFd.fd = dataPipe[0];
    pollFd.events = POLLIN;

    // Input event to be written to emulated mouse
    struct input_event event;

    // Initialize X11 display
    Display *display = XOpenDisplay ("");
    //Variable declaration
    Window w, r = DefaultRootWindow (display);
    double wx, wy, rx, ry;
    XIButtonState buttons_return;
    XIModifierState modifiers_return;
    XIGroupState group_return;

    //XIQueryPointer returns the absolute coordinates of (x,y) according to pointer device
    XIQueryPointer (display, 2, r, &r, &w, &rx, &ry, &wx, &wy, &buttons_return, &modifiers_return, &group_return);

    // Initialize current mouse coordinate to current coordinate
    int currentXPos = (int)rx,
        currentYPos = (int)ry;

    // Setup is done, try get data from SerialIO
    while (1) {
        sigsetjmp(environment, 1);
        // Handle SIGTERM
        if (uInputSigTermCaught) {
			// Exit child process
			#if DEBUG >= 1
			fprintf(stderr, "DEBUG: uInputMain(): SIGTERM caught, exiting.\n");
			#endif
			break;
        }

        // See if any data is on data pipe
        poll(&pollFd, 1, -1);
        // Read on data pipe
        read(dataPipe[0], (void *)&packet, sizeof(packet));

        if (packet.emulatedMouse != -1) {
            #if DEBUG >= 2
            // Dump packet we just got if it is not -1
        	fprintf(stderr, "Emulated Mouse: %d, X Pos: \t%d, Y Pos: \t%d\n", packet.emulatedMouse, packet.xPos, packet.yPos);
            #endif

        	if (packet.xPos >= XRES || packet.yPos >= YRES) {
				#if DEBUG >= 2
        		fprintf(stderr, "Error: Coordinate requested exceed screen resolution. Discarding packet...\n");
				#endif
        	} else {
				// Send relative coordinate to mouse
				memset(&event, 0, sizeof(event));
				gettimeofday(&event.time, NULL);
				event.type = EV_REL;
				event.code = REL_X;
				event.value = (packet.xPos - currentXPos);
				write(uInputDevice, &event, sizeof(event));

				memset(&event, 0, sizeof(event));
				gettimeofday(&event.time, NULL);
				event.type = EV_REL;
				event.code = REL_Y;
				event.value = (packet.yPos - currentYPos);
				write(uInputDevice, &event, sizeof(event));

				memset(&event, 0, sizeof(event));
				gettimeofday(&event.time, NULL);
				event.type = EV_KEY;
				event.code = BTN_LEFT;
				event.value = packet.leftPressed;
				write(uInputDevice, &event, sizeof(event));

				memset(&event, 0, sizeof(event));
				gettimeofday(&event.time, NULL);
				event.type = EV_SYN;
				event.code = SYN_REPORT;
				event.value = 0;
				write(uInputDevice, &event, sizeof(event));

				//XIQueryPointer returns the absolute coordinates of (x,y) according to pointer device
				XIQueryPointer (display, 2, r, &r, &w, &rx, &ry, &wx, &wy, &buttons_return, &modifiers_return, &group_return);

				// Update current position of mouse
				currentXPos = (int)rx;
				currentYPos = (int)ry;

				#if DEBUG >= 2
				fprintf(stderr, "Current XPos: \t\t\t%d, YPos: \t%d\n", currentXPos, currentYPos);
				if (currentXPos != packet.xPos || currentYPos != packet.yPos) {
				   fprintf(stderr, "### Real position not match requested one.###\n");
				}
				#endif
        	}
        }
    }
    

    // Destroy the mouse
    ioctl(uInputDevice, UI_DEV_DESTROY);
    close(uInputDevice);

    return 0;
}

static void sigTermHandler() {
	uInputSigTermCaught = 1;
    siglongjmp(environment, 1);
}

